package org.ffmmx.buildpatch;

import org.apache.commons.cli.*;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
import org.apache.commons.lang.StringUtils;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 一个简单的临时的补丁打包工具
 */
public class PatchMaker {
    /**
     * 打包工具版本
     */
    private String version = "1.1";
    private Map<String, Map<String, String>> configMap;
    private Map<String, String> config;

    private String PROJECT_ROOT;
    /**
     * 部署在服务器上面的地址
     */
    private String PROJECT_ARTIFACT_DIR;
    /**
     * class文件夹名称
     */
    private String CLASSES_DIR = "WEB-INF/classes";
    /**
     * class文件夹的真实路径
     */
    private String PROJECT_ARTIFACT_CLASSES_DIR;
    /**
     * 补丁输出文件夹
     */
    private String PATCH_OUT_DIR;
    /**
     * 改动补丁文件夹列表(可以直接使用idea格式)
     */
    private String INPUT_FILE;
    /**
     * 源码目录
     */
    private String SOURCE_DIR = "源码";
    /**
     * java源码目录
     */
    private String JAVA_SOURCE_DIR = "src";
    /**
     * 数据库脚本目录
     */
    private String OUTPUT_DATABASE_SCRIPT_DIR = "数据库脚本";
    /**
     * 网页根目录
     */
    private String WEB_ROOT = "WebRoot";
    /**
     * 过滤关键字列表
     */
    private List<String> EXCLUDE_WORDS = new ArrayList<>();

    /**
     * 输出程序文件夹
     */
    private String OUTPUT_WEBAPP_DIR = "程序";

    private List<String> modifyFiles = new ArrayList<>();
    /**
     * 输出的补丁文件夹名称（可选）
     */
    private String outPatchDirName;

    private boolean isDebug = false;

    /**
     * 支持的压缩类型
     */
    private enum SupportCompressFormats {
        z7z, zip, rar;
    }

    /**
     * 压缩类型，默认为空，这不需要压缩
     */
    private SupportCompressFormats compressFormat;

    /**
     * 打包补丁
     */
    private void buildPatch() throws IOException {
        File patchOutDir = new File(PATCH_OUT_DIR);
        if (!patchOutDir.exists()) {
            patchOutDir.mkdirs();
        }
        if (StringUtils.isNotEmpty(outPatchDirName)) {
            patchOutDir = new File(patchOutDir.getPath() + File.separator + outPatchDirName);
            if (patchOutDir.exists()) {
                patchOutDir.mkdirs();
            }
        }
        if (StringUtils.isNotEmpty(INPUT_FILE)) {
            File inputFile = new File(INPUT_FILE);
            BufferedReader bufferedReader = new BufferedReader(new FileReader(inputFile));
            String filepath;
            while ((filepath = bufferedReader.readLine()) != null) {
                if (StringUtils.isNotEmpty(filepath))
                    modifyFiles.add(filepath);
            }
        }

        if (isDebug) {
            System.out.println("改动文件列表...");
            modifyFiles.forEach(filename -> {
                System.out.println(filename);
            });
        }

        if (modifyFiles.isEmpty())
            throw new RuntimeException("已经修改的版本文件列表没有数据");

        modifyFiles.forEach(filepath -> {
            List<String> fromPaths = new ArrayList<>();
            List<String> toPaths = new ArrayList<>();
            File file = new File(filepath);
            boolean hasExcludeWords = false;
            for (String excludeWord : EXCLUDE_WORDS) {
                if (filepath.contains(excludeWord)) {
                    hasExcludeWords = true;
                    break;
                }
            }
            if (file.getName().contains(".") && !hasExcludeWords)
                if (filepath.contains(SOURCE_DIR + "/") && filepath.contains(JAVA_SOURCE_DIR + "/")) {        //源码
                    String path = filepath.substring(filepath.indexOf(JAVA_SOURCE_DIR + "/") + (JAVA_SOURCE_DIR + "/").length())
                            .replaceAll("\\.java", ".class");
                    fromPaths.add(PROJECT_ARTIFACT_CLASSES_DIR + "/" + path);
                    toPaths.add(PATCH_OUT_DIR + "/" + OUTPUT_WEBAPP_DIR + "/" + CLASSES_DIR + "/" + path);

                    if (path.contains(".class")) {   //支持内部类
                        final File fromPathFile0 = new File(PROJECT_ARTIFACT_CLASSES_DIR + "/" + path);
                        File[] subclassFiles = fromPathFile0.getParentFile().listFiles((dir, name) -> name.contains(fromPathFile0.getName().split("\\.class")[0] + "$"));
                        if (subclassFiles != null)
                            for (File subclassFile : subclassFiles) {
                                String subclass = subclassFile.getPath().replace("\\","/");
                                fromPaths.add(subclass);
                                toPaths.add(PATCH_OUT_DIR + "/" + OUTPUT_WEBAPP_DIR + "/" + CLASSES_DIR + "/" + subclass.split(PROJECT_ARTIFACT_CLASSES_DIR + "/")[1]);
                            }
                    }

                } else if (filepath.contains(SOURCE_DIR + "/") && filepath.contains(WEB_ROOT + "/")) { //WEB根目录
                    String path = filepath.substring(filepath.indexOf(WEB_ROOT + "/") + (WEB_ROOT + "/").length());
                    fromPaths.add(PROJECT_ARTIFACT_DIR + "/" + path);
                    toPaths.add(PATCH_OUT_DIR + "/" + OUTPUT_WEBAPP_DIR + "/" + path);
                } else if (filepath.contains(OUTPUT_DATABASE_SCRIPT_DIR + "/")) {   //数据库脚本
                    String path = filepath.substring(filepath.indexOf(OUTPUT_DATABASE_SCRIPT_DIR + "/")
                            + (OUTPUT_DATABASE_SCRIPT_DIR + "/").length());
                    fromPaths.add(PROJECT_ROOT + "/" + OUTPUT_DATABASE_SCRIPT_DIR + "/" + path);
                    toPaths.add(PATCH_OUT_DIR + "/" + OUTPUT_DATABASE_SCRIPT_DIR + "/" + path);
                }

            if (isDebug)
                System.out.println("实际操作文件...");
            for (int i = 0; i < fromPaths.size(); i++) {
                String fromPath = fromPaths.get(i);
                String toPath = toPaths.get(i);
                if (isDebug) {
                    System.out.println("来源文件：" + fromPath);
                    System.out.println("目标文件：" + toPath);
                }
                try {
                    writeFile(fromPath, toPath);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        });

        // 压缩输出
        compress();
    }

    private void writeFile(String fromPath, String toPath) throws IOException {
        if (StringUtils.isNotEmpty(fromPath) && StringUtils.isNotEmpty(toPath)) {
            File patchFilePath = new File(toPath);
            if (!patchFilePath.getParentFile().exists())
                patchFilePath.getParentFile().mkdirs();
            File fromFile = new File(fromPath);
            if (!fromFile.exists()) {
                return;
            }
            FileInputStream in = new FileInputStream(fromFile);
            FileOutputStream out = new FileOutputStream(patchFilePath);
            FileChannel inChannel = in.getChannel();
            FileChannel outChannel = out.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 2);
            while (inChannel.read(buffer) > 0) {
                buffer.flip();
                outChannel.write(buffer);
                buffer.clear();
            }

            outChannel.close();
            inChannel.close();
        }
    }

    public static PatchMaker parseCommmandLine(String[] args) throws Exception {
        Option help = new Option("h", "显示这个帮助信息");

        Options opts = new Options();
        opts.addOption(help);
        opts.addOption("p","projectRoot",true,"包含版本库信息的根目录");
        opts.addOption("P","projectArtifactDir",true,"编译完成以后发布的目录");
        opts.addOption("C","projectArtifactClassPath",true,"编译完成后发布的class文件路径");
        opts.addOption("c","outClassPathOnly",true,"目标class文件夹名称");
        opts.addOption("f","oldVersion",true,"旧版本号");
        opts.addOption("t","newversion",true,"新版本号");
        opts.addOption("T","vcsType",true,"版本管理类型");
        opts.addOption("o", "outPatchPath", true, "补丁输出路径");
        opts.addOption("i", "inputFile", true, "有改动文件列表的问题");
        opts.addOption("s", "sourceDir", true, "源码文件夹名称");
        opts.addOption("j", "javaSourceDir", true, "java程序源码文件夹名称例如src之类的");
        opts.addOption("S", "databaseScriptDir", true, "脚本文件夹名称，程序通过这个名称来确定该文件类型");
        opts.addOption("w", "webRootDir", true, "设置web源程序根文件夹，默认 WebRoot");
        opts.addOption("e", "excludeWords", true, "设置过滤的关键字，包含被过滤的关键字的文件将不会出现在补丁里面");
        opts.addOption("W", "webappDirName", true, "设置程序输出文件夹，默认叫程序");
        opts.addOption("O", "outPatchDirName", true, "设置补丁输出文件夹名称，设置参数会接在补丁输出路径的后面");
        opts.addOption("d", "debug", false, "显示调试信息");
        opts.addOption("z", "compress", true, "输出压缩包 支持 7z,zip,rar,7z 使用z7z作为参数");
        opts.addOption("v", "version", false, "输出当前版本");

        DefaultParser defaultParser = new DefaultParser();
        CommandLine cli = defaultParser.parse(opts, args);

        if (cli.getOptions().length == 0 || cli.hasOption("h")) {
            HelpFormatter helpFormatter = new HelpFormatter();
            helpFormatter.printHelp("patchMaker -p 项目路径 -f 起始版本号 -t 终止版本号 ", opts);
            System.exit(0);
        }

        PatchMaker maker = new PatchMaker();
        //        版本
        if(cli.hasOption("v")){
            System.out.println("当前打包工具版本为："+maker.version);
            System.exit(0);
        }

        maker.PROJECT_ROOT = cli.getOptionValue("p");
        if (StringUtils.isEmpty(maker.PROJECT_ROOT))
            throw new RuntimeException("需要指定一个项目路径");
        maker.PROJECT_ROOT = maker.PROJECT_ROOT.replace("\\","/");
        maker.PROJECT_ARTIFACT_DIR = cli.getOptionValue("P", maker.PROJECT_ROOT + "/" + "out" + "/" + "artifacts" + "/" + "web").replace("\\","/");
        maker.CLASSES_DIR = cli.getOptionValue("c", maker.CLASSES_DIR);
        maker.PROJECT_ARTIFACT_CLASSES_DIR = cli.getOptionValue("C", maker.PROJECT_ARTIFACT_DIR + "/" + maker.CLASSES_DIR).replace("\\","/");
        maker.PATCH_OUT_DIR = cli.getOptionValue("o", System.getProperty("user.dir")).replace("\\","/");
        maker.INPUT_FILE = cli.getOptionValue("i");
        maker.SOURCE_DIR = cli.getOptionValue("s", maker.SOURCE_DIR);
        maker.JAVA_SOURCE_DIR = cli.getOptionValue("j", maker.JAVA_SOURCE_DIR);
        maker.OUTPUT_DATABASE_SCRIPT_DIR = cli.getOptionValue("S", maker.OUTPUT_DATABASE_SCRIPT_DIR);
        maker.WEB_ROOT = cli.getOptionValue("w", maker.WEB_ROOT);
        String excludeWordS = cli.getOptionValue("e", "补丁");
        maker.EXCLUDE_WORDS = Arrays.stream(excludeWordS.split(",")).collect(Collectors.toList());

        maker.OUTPUT_WEBAPP_DIR = cli.getOptionValue("W", maker.OUTPUT_WEBAPP_DIR).replace("\\","/");
        maker.outPatchDirName = cli.getOptionValue("O");



        String oldVersion = cli.getOptionValue("f");
        if (StringUtils.isEmpty(oldVersion))
            throw new RuntimeException("需要指定一个起始版本号，文件提取器会提取大于这个版本号的文件");
        String newVersion = cli.getOptionValue("t");
        if (StringUtils.isEmpty(newVersion))
            throw new RuntimeException("需要指定一个终止版本号");
        String vcsType = cli.getOptionValue("T", "svn");

        maker.isDebug = cli.hasOption("d");

//        压缩格式
        String compressType = cli.getOptionValue("z");
        try {
            if(compressType != null)
                maker.compressFormat = SupportCompressFormats.valueOf(compressType);
        } catch (IllegalArgumentException e) {
            System.out.println("不支持该类型");
            System.exit(0);
        }


        PatchMaker.VCSManager vcsManager = maker.new VCSManager(vcsType);
        maker.modifyFiles = vcsManager.getAction().diff(oldVersion, newVersion);
        if (vcsManager.getVcsHelperClosable() != null) {
            vcsManager.getVcsHelperClosable().close();
        }

        return maker;
    }

    /**
     * 输出压缩
     */
    public void compress() {
        if (compressFormat != null) {
            switch (compressFormat) {
                case rar:
                    break;
                case z7z:
                    File outFile = new File(this.PATCH_OUT_DIR + ".7z");
                    File needCompressFilePath = new File(this.PATCH_OUT_DIR);
                    try {
                        SevenZOutputFile sevenZipFile = new SevenZOutputFile(outFile);
                        compress7z(needCompressFilePath,sevenZipFile, needCompressFilePath.getParent());
                        sevenZipFile.close();
//                        压缩结束以后删除之前的补丁文件夹
                        deleteFile(needCompressFilePath);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    break;
                case zip:
                    break;
            }
        }

    }
    private void compress7z(File ncFile, SevenZOutputFile z7zOut, String rootPath) throws IOException {

        if(ncFile.isDirectory()){
            for (File ncf : ncFile.listFiles()) {
                compress7z(ncf,z7zOut, rootPath);
            }
        }
        else{
            SevenZArchiveEntry entry = z7zOut.createArchiveEntry(ncFile, ncFile.getPath().substring(rootPath.length()));
            z7zOut.putArchiveEntry(entry);
            FileInputStream fis = new FileInputStream(ncFile);
            byte[] content = new byte[1024];
            int rcount = 0;
            while (fis.available() > 0) {
                rcount = fis.read(content);
                z7zOut.write(content, 0, rcount);
            }
            fis.close();
            z7zOut.closeArchiveEntry();
        }
    }

    private boolean deleteFile(File file) {
        if(file.isDirectory()) {
            for (File sub : file.listFiles()) {
                boolean result = deleteFile(sub);
                if(!result)
                    break;
            }
        }
        return file.delete();
    }

    class VCSManager {
        /**
         * 版本管理名称
         */
        private String vcsName;
        /**
         * 版本管理操作
         */
        private VCSAction action;
        private VCSHelperClosable vcsHelperClosable;
        private String vcsPath;

        public VCSManager(String vcsName) throws SQLException, ClassNotFoundException {
            this.vcsName = vcsName;

            if ("svn".equals(this.vcsName)) {
                this.vcsPath = PROJECT_ROOT + "/.svn/wc.db";
                File vcsdb = new File(this.vcsPath);
                if (!vcsdb.exists())
                    throw new RuntimeException("该项目路径并不包含svn版本信息文件");
                SVNConnector svn = new SVNConnector(this.vcsPath);
                action = svn;
                vcsHelperClosable = svn;
            }
        }

        public VCSAction getAction() {
            return action;
        }

        public VCSHelperClosable getVcsHelperClosable() {
            return vcsHelperClosable;
        }
    }

    class SVNConnector implements VCSAction, VCSHelperClosable {

        private String svnlibPath;
        private Connection connection;

        public SVNConnector(String svnlibPath) throws ClassNotFoundException, SQLException {
            this.svnlibPath = svnlibPath;

            Class.forName("org.sqlite.JDBC");
            connection = DriverManager.getConnection("jdbc:sqlite:" + this.svnlibPath);
            connection.setAutoCommit(false);
        }

        @Override
        public List<String> diff(String oldVersion, String newVersion) throws SQLException {
            if (StringUtils.isEmpty(oldVersion))
                throw new RuntimeException("旧版本号为空");
            if (StringUtils.isEmpty(newVersion))
                throw new RuntimeException("新版本号为空");
            List<String> files = new ArrayList<>();
            try {
                PreparedStatement statement = connection.prepareStatement("SELECT repos_path FROM NODES " +
                        "WHERE changed_revision > ? and changed_revision <= ? and kind='file'");
                statement.setString(1, oldVersion);
                statement.setString(2, newVersion);
                ResultSet resultSet = statement.executeQuery();

                while (resultSet.next()) {
                    files.add(resultSet.getString(1));
                }
            } catch (SQLException e) {
                System.out.println("比较语句执行出错开始回滚.");
                e.printStackTrace();
                connection.rollback();
            }
            return files;
        }

        public void close() throws SQLException {
            if (connection != null && !connection.isClosed())
                connection.close();
        }
    }

    @FunctionalInterface
    interface VCSAction {
        List<String> diff(String oldVersion, String newVersion) throws SQLException;
    }

    @FunctionalInterface
    interface VCSHelperClosable {
        void close() throws Exception;
    }

    public static void main(String[] args) throws Exception {
//        PatchMaker pm = new PatchMaker("gxnbdw");
//        pm.buildPatch();

//        args = new String[] {
//                "-p /home/firefoxmmx2/Work/niosocket_test/patchmaker/target",
//                "-f 7192",
//                "-t 7299"
//        };
        PatchMaker patchMaker = PatchMaker.parseCommmandLine(args);
        patchMaker.buildPatch();
    }
}
